import datetime
import functools
import logging
import signal

from datetime import date, timedelta, datetime
from enum import Enum

from absl import app, flags
from tornado.ioloop import PeriodicCallback

from collections import deque

from coin.base.datetime_util import to_datetime
from coin.base.timestamp import get_timestamp
from coin.base.param_util import to_list
from coin.exchange.base.order_gateway_config import convert_og_config_from_dict
from coin.exchange.base.strategy_logger import StrategyLogProvider
from coin.exchange.kr_rest.product.product_impl import (generate_product_from_subreq)
from coin.exchange.kr_rest.product_holders import get_holder_from_product
from coin.exchange.util.feed_checker import get_default_feed_checker_config
from coin.proto.coin_feed_pb2 import BestBookEntry, BookEntry
from coin.proto.coin_request_pb2 import AccountRequestProto
from coin.proto.coin_telemetry_pb2 import LatencyProto

from coin.strategy.accounting.pnl_balance_printer.util import (get_quote_currency, reserve_as_proto)
from coin.strategy.executor.aggressive.util import (convert_executor_config_from_dict)
from coin.strategy.executor.aggressive.factory import construct_agg_executor
from coin.strategy.executor.util.cancel_all_executor import (SimpleCancelAllExecutor)
from coin.strategy.hk_hard_arb.index_pricer import IndexPricer
from coin.strategy.hk_hard_arb.pricer import PositionLeanPricer
from coin.strategy.hk_hard_arb.util import load_clean_json
from coin.strategy.mm.base.bookmap import BookMap
from coin.strategy.mm.base.unary_multifeed_strategy_base import UnaryMultifeedStrategyBase

from coin.strategy.mm.subscription import (
    FeedSubscriptionRequest,
    SubscriptionRequest,
)
from coin.strategy.tool.fill_log_dumper import FillLogDumper
from coin.strategy.hk_hard_arb.amplitude_checker import AmplitudeChecker
from coin.strategy.accounting.pnl_balance_printer.factory import get_pnl_balance_printer_cls
from coin.strategy.util.request_util import convert_strategy_request_from_dict
from coin.support.telemetry.latency_recorder import LatencyRecorder, LifeOfSignal
from coin.strategy.hk_hard_arb.unhedged_mm_3 import (
    PassUnhedgeStrategyConfig,
)
from coin.exchange.base.order_gateway import OrderSide
from coin.experimental.leon.simple_sim.run_from_archive import (
    run_from_archive_or_cache, define_fastfeed_flags)

from coin.strategy.dex_strat.simple_amm.amm_utils import (BiasCalculator)

from coin.strategy.dex_strat.simple_arb.arb_utils import (
  ArbTickEntry,
  ArbSignalCalculator
)

from coin.strategy.dex_strat.simple_amm.amm_utils import (
  SimpleBookEntry
  )

FLAGS = flags.FLAGS

class TradeSide(Enum):
    BUY = 0
    SELL = 1

class SignalCalculator(object):
  def __init__(self, queue_size = 15, bar_percentage = 0.5, opp_bar_percentage = 0.2):
    self._kqueue_size = queue_size
    self._kbar_percentage = bar_percentage
    self._kopp_bar_percentage = opp_bar_percentage
    self.signal_queue = deque(0 for i in range(self._kqueue_size))
    self.plus_signal_cnt = 0
    self.minus_signal_cnt = 0
  
  def clear(self):
    self.signal_queue = deque(0 for i in range(self._kqueue_size))
    self.plus_signal_cnt = 0
    self.minus_signal_cnt = 0

  def add_signal(self, signal):
    old_signal = self.signal_queue.popleft()
    if old_signal > 0:
      self.plus_signal_cnt -= 1
    elif old_signal < 0:
      self.minus_signal_cnt -= 1
    
    if signal > 0:
      self.plus_signal_cnt += 1
    elif signal < 0:
      self.minus_signal_cnt += 1
    
    self.signal_queue.append(signal)
  
  def has_plus_signal(self, in_opp):
    if in_opp:
      return float(self.plus_signal_cnt) / float(self._kqueue_size) > self._kopp_bar_percentage
    else:
      return float(self.plus_signal_cnt) / float(self._kqueue_size) > self._kbar_percentage
  
  def has_minus_signal(self, in_opp):
    if in_opp:
      return float(self.minus_signal_cnt) / float(self._kqueue_size) > self._kopp_bar_percentage
    else:
      return float(self.minus_signal_cnt) / float(self._kqueue_size) > self._kbar_percentage

  def get_plus_percentage(self):
    return float(self.plus_signal_cnt) / float(self._kqueue_size)
  
  def get_minus_percentage(self):
    return float(self.minus_signal_cnt) / float(self._kqueue_size)

class SingleProductSubStrategyArbBase(object):
  def __init__(self, model_profile, logger, parent):
    self._logger = logger
    self._parent = parent
    config = model_profile.config_dict
    self._config = config
    self._trade_subreq = model_profile.trade_subreq
    self._trade_product = model_profile.trade_product
    self._ref_subreq = model_profile.ref_subreq
    self._ref_product = model_profile.ref_product
    assert len(self._ref_subreq) == len(self._ref_product)

    self._edge = None
    self._spread = None
    self._spread_hard = None
    self._pull_edge = None

    self._bbo_protection = config.get('bbo_protection', False)
    self._edge = config['edge_bp'] / 10000.

    products = self._ref_product + to_list(self._trade_product)
    self._bookmap = BookMap(products, logger=self._logger)

    tick = get_holder_from_product(self._trade_product).product_info.first_tick_size
    self._pricer = IndexPricer(self._config, tick, use_fastfeed=True)
    self._pos_lean_pricer = PositionLeanPricer(min_pos=config['min_pos'],
                                               max_pos=config['max_pos'],
                                               lot_size=config['lot_size'],
                                               lean_edge_bp=config.get('lean_edge_bp', 0))

    self._exit = False

    self._last_book_write_ts = get_timestamp()
    self._last_binance_book_ts = 0
    self._last_uniswap_book_ts = 0
    self._last_binance_ask0 = 0.0
    self._last_binance_bid0 = 0.0
    self._last_uniswap_ask0 = 0.0
    self._last_uniswap_bid0 = 0.0
    self._kmax_diff_ms = int(self._config.get('max_diff_ms', 500))

    self._kgas_fee = float(self._config.get('gas_fee', 0.5))
    self.reserve_pos = float(self._config.get('reserve', 0.5))
    self.max_pos = float(self._config.get('max_pos', 0.9))
    self.min_pos = float(self._config.get('min_pos', 0.1))
    self.current_pos = self.reserve_pos
    self.lot_size = float(self._config.get('lot_size', 0.1))

  def set_exit(self):
    self._exit = True

  def prepare_base(self, feed_subsystem):
    assert feed_subsystem is not None
    product = self._trade_product
    subreq = self._trade_subreq
    trade_feed_sub_request = FeedSubscriptionRequest.create(to_list(product), subreq)
    feed_subsystem.subscribe_book(trade_feed_sub_request, functools.partial(
        self.on_book, self._trade_product, self._trade_subreq))
    for product, sub_req in zip(self._ref_product, self._ref_subreq):
      feed_sub_request = FeedSubscriptionRequest.create(to_list(product), sub_req)
      feed_subsystem.subscribe_book(
          feed_sub_request, functools.partial(self.on_book, product, sub_req))

    feed_subsystem.subscribe_trade(trade_feed_sub_request, functools.partial(
        self.on_trade, self._trade_product, self._trade_subreq))
    for product, sub_req in zip(self._ref_product, self._ref_subreq):
      feed_sub_request = FeedSubscriptionRequest.create(to_list(product), sub_req)
      feed_subsystem.subscribe_trade(
          feed_sub_request, functools.partial(self.on_trade, product, sub_req))
    return True

  def on_book_base(self, product, book):
    if self._exit:
      return False

    self._bookmap.set_book(product, book)

    # Check book freshness and sanity
    if not self._bookmap.is_book_ready() or not self._bookmap.is_book_sane():
      return False

    ref_book = [self._bookmap.get_book(product) for product in self._ref_product]
    trade_book = self._bookmap.get_book(self._trade_product)
    #self._pricer.update(ref_book, trade_book)

    if product.exchange == 'Uniswap':
      if book.timestamp - self._last_uniswap_book_ts >= 200 * 10 ** 6:
        self._last_uniswap_book_ts = book.timestamp
        self._last_uniswap_ask0 = book.ask0().price
        self._last_uniswap_bid0 = book.bid0().price
        self._logger.info('%s Book product: %s, askp: %s, bidp: %s' % (
            to_datetime(book.timestamp), product.symbol, book.ask0().price, book.bid0().price))
      else:
        return False
    elif product.exchange == 'Binance':
      # for old arb before m2_fix
      #if book.timestamp - self._last_binance_book_ts >= 500 * 10 ** 6:
      if book.timestamp - self._last_binance_book_ts >= 100 * 10 ** 6:
        self._last_binance_book_ts = book.timestamp
        self._last_binance_ask0 = book.ask0().price
        self._last_binance_bid0 = book.bid0().price
        self._logger.info('%s Book product: %s, askp: %s, bidp: %s' % (
            to_datetime(book.timestamp), product.symbol, book.ask0().price, book.bid0().price))
      else:
        return False
      
    #if not self._pricer.ready:
    #  return False

    return True

  def on_trade_base(self, product, trade):
    if product.exchange == 'Uniswap':
      self._logger.info('%s Trade product: %s, price: %s, qty: %s, side: %s' % (
        to_datetime(trade.timestamp), product.symbol, trade.price, trade.qty, trade.side))
    return True

  def on_og_reset(self, *, order_gateway):
    self._logger.info('Intializing executor...')
    executor_params = convert_executor_config_from_dict(self._trade_product.symbol, self._config)
    self._executor = construct_agg_executor(self._trade_subreq,
                                            executor_params,
                                            order_gateway,
                                            self._logger,
                                            latency_recorder=self._latency_recorder)
    self._strategy_logger.write_executor_config(executor_params)

  def prepare_process(self, feed_subsystem):
    return

  def on_book_process(self, product, book):
    return

  def on_trade_process(elf, product, trade):
    return

  def prepare(self, feed_subsystem):
    if not self.prepare_base(feed_subsystem):
      return
    self.prepare_process(feed_subsystem)

  def on_book(self, product, book):
    if not self.on_book_base(product, book):
      return
    self.on_book_process(product, book)

  def on_trade(self, product, trade):
    if not self.on_trade_base(product, trade):
      return
    self.on_trade_process(product, trade)

class SingleProductSubStrategyArbGeneralBase(object):
  def __init__(self, model_profile, logger, parent):
    self._logger = logger
    self._parent = parent
    config = model_profile.config_dict
    self._config = config
    self._trade_subreq = model_profile.trade_subreq
    self._trade_product = model_profile.trade_product
    self._ref_subreq = model_profile.ref_subreq
    self._ref_product = model_profile.ref_product
    assert len(self._ref_subreq) == len(self._ref_product)

    self._edge = None
    self._spread = None
    self._spread_hard = None
    self._pull_edge = None

    self._bbo_protection = config.get('bbo_protection', False)
    self._edge = config['edge_bp'] / 10000.

    products = self._ref_product + to_list(self._trade_product)
    self._bookmap = BookMap(products, logger=self._logger)

    tick = get_holder_from_product(self._trade_product).product_info.first_tick_size
    self._pricer = IndexPricer(self._config, tick, use_fastfeed=True)
    self._pos_lean_pricer = PositionLeanPricer(min_pos=config['min_pos'],
                                               max_pos=config['max_pos'],
                                               lot_size=config['lot_size'],
                                               lean_edge_bp=config.get('lean_edge_bp', 0))

    self._exit = False

    self._last_book_write_ts = get_timestamp()
    self._last_binance_book_ts = 0
    self._last_uniswap_book_ts = 0
    self._last_binance_ask0 = 0.0
    self._last_binance_bid0 = 0.0
    self._last_uniswap_ask0 = 0.0
    self._last_uniswap_bid0 = 0.0
    self._kmax_diff_ms = int(self._config.get('max_diff_ms', 100))

    self._kgas_fee = float(self._config.get('gas_fee', 0.5))
    self.reserve_pos = float(self._config.get('reserve', 0.5))
    self.max_pos = float(self._config.get('max_pos', 0.9))
    self.min_pos = float(self._config.get('min_pos', 0.1))
    self.current_pos = self.reserve_pos
    self.lot_size = float(self._config.get('lot_size', 0.1))

    self._last_book_dict = {}
    for product in products:
      self._last_book_dict[product.exchange] = {}
      self._last_book_dict[product.exchange][product.symbol] = None

  def set_exit(self):
    self._exit = True

  def prepare_base(self, feed_subsystem):
    assert feed_subsystem is not None
    product = self._trade_product
    subreq = self._trade_subreq
    trade_feed_sub_request = FeedSubscriptionRequest.create(to_list(product), subreq)
    feed_subsystem.subscribe_book(trade_feed_sub_request, functools.partial(
        self.on_book, self._trade_product, self._trade_subreq))
    for product, sub_req in zip(self._ref_product, self._ref_subreq):
      feed_sub_request = FeedSubscriptionRequest.create(to_list(product), sub_req)
      feed_subsystem.subscribe_book(
          feed_sub_request, functools.partial(self.on_book, product, sub_req))

    feed_subsystem.subscribe_trade(trade_feed_sub_request, functools.partial(
        self.on_trade, self._trade_product, self._trade_subreq))
    for product, sub_req in zip(self._ref_product, self._ref_subreq):
      feed_sub_request = FeedSubscriptionRequest.create(to_list(product), sub_req)
      feed_subsystem.subscribe_trade(
          feed_sub_request, functools.partial(self.on_trade, product, sub_req))
    return True

  def on_book_base(self, product, book):
    if self._exit:
      return False

    self._bookmap.set_book(product, book)

    # Check book freshness and sanity
    if not self._bookmap.is_book_ready() or not self._bookmap.is_book_sane():
      return False

    ref_book = [self._bookmap.get_book(product) for product in self._ref_product]
    trade_book = self._bookmap.get_book(self._trade_product)
    #self._pricer.update(ref_book, trade_book)
    
    if self._last_book_dict[product.exchange].get(product.symbol) is None \
    or book.timestamp - self._last_book_dict[product.exchange][product.symbol].timestamp >= self._kmax_diff_ms * 10 ** 6:
      self._last_book_dict[product.exchange][product.symbol] = SimpleBookEntry(book.timestamp, product.exchange, product.symbol, book.ask0().price, book.bid0().price)
      self._logger.info('%s Book product:%s,%s, askp: %s, bidp: %s' % (
            to_datetime(book.timestamp), product.exchange, product.symbol, book.ask0().price, book.bid0().price))
    else:
      return False
      
    #if not self._pricer.ready:
    #  return False

    return True

  def on_trade_base(self, product, trade):
    if product.exchange == 'Uniswap':
      self._logger.info('%s Trade product: %s, price: %s, qty: %s, side: %s' % (
        to_datetime(trade.timestamp), product.symbol, trade.price, trade.qty, trade.side))
    return True

  def on_og_reset(self, *, order_gateway):
    self._logger.info('Intializing executor...')
    executor_params = convert_executor_config_from_dict(self._trade_product.symbol, self._config)
    self._executor = construct_agg_executor(self._trade_subreq,
                                            executor_params,
                                            order_gateway,
                                            self._logger,
                                            latency_recorder=self._latency_recorder)
    self._strategy_logger.write_executor_config(executor_params)

  def prepare_process(self, feed_subsystem):
    return

  def on_book_process(self, product, book):
    return

  def on_trade_process(elf, product, trade):
    return

  def prepare(self, feed_subsystem):
    if not self.prepare_base(feed_subsystem):
      return
    self.prepare_process(feed_subsystem)

  def on_book(self, product, book):
    if not self.on_book_base(product, book):
      return
    self.on_book_process(product, book)

  def on_trade(self, product, trade):
    if not self.on_trade_base(product, trade):
      return
    self.on_trade_process(product, trade)

class SimpleArbStatisticPriceWindow(SingleProductSubStrategyArbBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)
    self.last_processed_uniswap_ts = 0
    self._kgas_fee = 0.5
    self._kprice_diff_bar = 1.0
    self._ksignal_cnt_bar = 15
    self.plus_signal_cnt = 0
    self.minus_signal_cnt = 0
    self.total_diff = 0.0

    self.start_uniswap_ts = 0
    self.start_uniswap_ask0 = 0.0
    self.start_uniswap_bid0 = 0.0
    self.start_binance_ask0 = 0.0
    self.start_binance_bid0 = 0.0
    self.last_uniswap_ts = 0
    self.last_uniswap_ask0 = 0.0
    self.last_uniswap_bid0 = 0.0
    self.last_binance_ask0 = 0.0
    self.last_binance_bid0 = 0.0
  
  def process_current_signal(self):
    if self.plus_signal_cnt >= self._ksignal_cnt_bar:
      self._logger.info('Price diff[plus pos]: start from:%s, uniswap_ask0: %s, binance_bid0: %s, diff:%s' % (
            to_datetime(self.start_uniswap_ts), self.start_uniswap_ask0, self.start_binance_bid0, self.start_binance_bid0 - self.start_uniswap_ask0))
      self._logger.info('Price diff[plus pos]: end at:%s, uniswap_ask0: %s, binance_bid0: %s, diff:%s' % (
            to_datetime(self.last_uniswap_ts), self.last_uniswap_ask0, self.last_binance_bid0, self.last_binance_bid0 - self.last_uniswap_ask0))
      self._logger.info('Price diff[plus pos]: avg diff:%s' % (self.total_diff / float(self.plus_signal_cnt)))
    elif self.minus_signal_cnt >= self._ksignal_cnt_bar:
      self._logger.info('Price diff[minus pos]: start from:%s, uniswap_bid0: %s, binance_ask0: %s, diff:%s' % (
            to_datetime(self.start_uniswap_ts), self.start_uniswap_bid0, self.start_binance_ask0, self.start_uniswap_bid0 - self.start_binance_ask0))
      self._logger.info('Price diff[minus pos]: end at:%s, uniswap_bid0: %s, binance_ask0: %s, diff:%s' % (
            to_datetime(self.last_uniswap_ts), self.last_uniswap_bid0, self.last_binance_ask0, self.last_uniswap_bid0 - self.last_binance_ask0))
      self._logger.info('Price diff[minus pos]: avg diff:%s' % (self.total_diff / float(self.minus_signal_cnt)))
      

  def set_last_info(self, uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0):
    self.last_uniswap_ts = uniswap_ts
    self.last_uniswap_ask0 = uniswap_ask0
    self.last_uniswap_bid0 = uniswap_bid0
    self.last_binance_ask0 = binance_ask0
    self.last_binance_bid0 = binance_bid0

  def set_start_info(self, uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0):
    self.start_uniswap_ts = uniswap_ts
    self.start_uniswap_ask0 = uniswap_ask0
    self.start_uniswap_bid0 = uniswap_bid0
    self.start_binance_ask0 = binance_ask0
    self.start_binance_bid0 = binance_bid0

  def process_new_tick(self, uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0):
    if uniswap_ts == self.last_processed_uniswap_ts:
      return
    self.last_processed_uniswap_ts = uniswap_ts
    self._logger.info('tick:%s' % (to_datetime(uniswap_ts)))
    if uniswap_ask0 < binance_bid0 - self._kprice_diff_bar:
      if self.plus_signal_cnt > 0:
        self.plus_signal_cnt += 1
        self.total_diff += binance_bid0 - uniswap_ask0
      else:
        self.process_current_signal()
        self.plus_signal_cnt = 1
        self.minus_signal_cnt = 0
        self.total_diff = binance_bid0 - uniswap_ask0
        self.set_start_info(uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0)
      self.set_last_info(uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0)
    elif uniswap_bid0 > binance_ask0 + self._kprice_diff_bar:
      if self.minus_signal_cnt > 0:
        self.minus_signal_cnt += 1
        self.total_diff += uniswap_bid0 - binance_ask0
      else:
        self.process_current_signal()
        self.minus_signal_cnt = 1
        self.plus_signal_cnt = 0
        self.total_diff = uniswap_bid0 - binance_ask0
        self.set_start_info(uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0)
      self.set_last_info(uniswap_ts, uniswap_ask0, uniswap_bid0, binance_ask0, binance_bid0)
    else:
      self.process_current_signal()
      self.minus_signal_cnt = 0
      self.plus_signal_cnt = 0
      self.total_diff = 0.0

  def on_book_process(self, product, book):
    if product.exchange == 'Uniswap':
      if abs(book.timestamp - self._last_binance_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick(book.timestamp, book.ask0().price, book.bid0().price, self._last_binance_ask0, self._last_binance_bid0)

    elif product.exchange == 'Binance':
      if abs(book.timestamp - self._last_uniswap_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick(self._last_uniswap_book_ts, self._last_uniswap_ask0, self._last_uniswap_bid0, book.ask0().price, book.bid0().price)

class SimpleArbGeneralM2_V2SubStrategy(SingleProductSubStrategyArbGeneralBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)
    
    self._karb_diff_bar = float(self._config.get('arb_diff_bar', 0.5))

    self._current_quote_value = 0.0
    self._total_profit = 0.0
    self._total_net_profit = 0.0
    self._tx_count = 0

    self._kpos_mismatch_bar = 0.05

    self._karb_diff_bar = FLAGS.arb_diff_bar
    self._karb_leave_diff_bar = FLAGS.arb_leave_bar
    self._karb_stop_price_diff = 3.0
    '''
    self._kpos_mismatch_bar = 5.0

    self._karb_diff_bar = FLAGS.arb_diff_bar
    self._karb_leave_diff_bar = FLAGS.arb_leave_bar
    self._karb_stop_price_diff = 0.003
    '''
    self._ktick_ts_diff_allowed = 300000000
    self._last_buy_price = 0.0
    self._last_sell_price = 0.0
    #self._arb_signal_calculator = ArbSignalCalculator(queue_size=2, enter_bar=self._karb_diff_bar, leave_bar=self._karb_leave_diff_bar)
    self._arb_signal_calculator = ArbSignalCalculator(queue_size=FLAGS.queue_size, enter_bar=self._karb_diff_bar, leave_bar=self._karb_leave_diff_bar)

    self._korder_cooldown = 5000000000
    self._korder_new_interval = 60000000000
    self._tick_cnt = 0
    self._last_order_ts = 0
    self._is_last_order_success = True
    self._is_last_order_return_type = True
    self._korder_safe_cooldown = 20000000000

    self._korder_tick_delay = 1
    self._order_attempt_size = 0.0
    self._order_submit_tick = 0

  def process_order_with_delay(self, current_tick, ask0, bid0):
    if self._order_submit_tick == 0 or abs(self._order_attempt_size) < 0.0001:
      return
    if current_tick >= self._order_submit_tick + self._korder_tick_delay:
      if self._order_attempt_size > 0.0:
        self._logger.info('Arb product[real price]: buy: %s' % (ask0))
      else:
        self._logger.info('Arb product[real price]: sell: %s' % (bid0))
      self.update_balance_profit(self._order_attempt_size, ask0, bid0)
      if not self._is_last_order_return_type:
        if self._order_attempt_size > 0.0:
          self._last_buy_price = ask0
        else:
          self._last_sell_price = bid0
      else:
        self._last_buy_price = 0.0
        self._last_sell_price = 0.0
      self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
        self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
      self._order_attempt_size = 0.0
      self._order_submit_tick = 0



  def decide_order_success(self, current_ts):
    self._last_order_ts = current_ts
    return True
    #if current_ts - self._last_order_ts > self._korder_new_interval:
    #  self._last_order_ts = current_ts
    #  self._is_last_order_success = False
    #  return False
    
    self._last_order_ts = current_ts
    self._is_last_order_success = not self._is_last_order_success
    return self._is_last_order_success

  def update_balance_profit(self, trade_lot, ask_price, bid_price):
    self._tx_count += 1
    self.current_pos += trade_lot
    if trade_lot > 0:
      self._current_quote_value -= trade_lot * ask_price
    elif trade_lot < 0:
      self._current_quote_value -= trade_lot * bid_price
    base_to_quote = 0.0
    if self.current_pos > self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * bid_price
    elif self.current_pos < self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * ask_price
    self._total_profit = self._current_quote_value + base_to_quote
    self._total_net_profit = self._total_profit - float(self._tx_count) * self._kgas_fee


  def on_book_process(self, product, book):
    if product != self._trade_product:
      return
    
    self._tick_cnt += 1
    if self._tick_cnt < 5:
      return

    askp, bidp = book.ask0().price, book.bid0().price
    '''
    mid_usdc_usdt = (self._last_book_dict['Binance']['USDC-USDT'].ask0 + self._last_book_dict['Binance']['USDC-USDT'].bid0) / 2.0
    binance_askp = self._last_book_dict['Binance']['ARB-USDT'].ask0 / mid_usdc_usdt
    binance_bidp = self._last_book_dict['Binance']['ARB-USDT'].bid0 / mid_usdc_usdt
    '''
    binance_askp = self._last_book_dict['Binance']['ETH-USDC'].ask0
    binance_bidp = self._last_book_dict['Binance']['ETH-USDC'].bid0

    self._logger.info("binance calculate: ask0:%f, bid0:%f." % (binance_askp, binance_bidp))  
    self._arb_signal_calculator.insert_record(book.timestamp, askp, bidp, binance_askp, binance_bidp)

    if self._tick_cnt < 10:
      return
    
    if self._korder_tick_delay > 0:
      self.process_order_with_delay(self._tick_cnt, askp, bidp)

    if self._is_last_order_return_type and book.timestamp - self._last_order_ts < self._korder_safe_cooldown \
      or not self._is_last_order_return_type and book.timestamp - self._last_order_ts < self._korder_cooldown:
      return

    #if book.timestamp - self._last_book_dict['Binance']['ETH-USDT'].timestamp > self._ktick_ts_diff_allowed:
    #if book.timestamp - self._last_book_dict['Binance']['ARB-USDT'].timestamp > self._ktick_ts_diff_allowed:
    if book.timestamp - self._last_book_dict['Binance']['ETH-USDC'].timestamp > self._ktick_ts_diff_allowed:
      return

    if self.current_pos > self.reserve_pos + self._kpos_mismatch_bar:
      if self._last_buy_price - askp > self._karb_stop_price_diff or self._arb_signal_calculator.can_return_buy():
        self._logger.info('Arb product[Return BUY]: %s, sell: %s' % (
          self._trade_product.symbol, bidp))
        self._is_last_order_return_type = True
        if self.decide_order_success(book.timestamp):
          self._order_submit_tick = self._tick_cnt
          self._order_attempt_size = -self.lot_size
        else:
          self._logger.info('Order Failed!!')
    elif self.current_pos < self.reserve_pos - self._kpos_mismatch_bar:
      if bidp - self._last_sell_price > self._karb_stop_price_diff or self._arb_signal_calculator.can_return_sell():
        self._logger.info('Arb product[Return SELL]: %s, buy: %s' % (
          self._trade_product.symbol, askp))
        self._is_last_order_return_type = True
        if self.decide_order_success(book.timestamp):
          self._order_submit_tick = self._tick_cnt
          self._order_attempt_size = self.lot_size
        else:
          self._logger.info('Order Failed!!')
    else:
      if self._arb_signal_calculator.can_enter_buy():
        self._logger.info('Arb product[Start Buy]: %s, buy: %s' % (
          self._trade_product.symbol, askp))
        self._is_last_order_return_type = False
        if self.decide_order_success(book.timestamp):
          self._order_submit_tick = self._tick_cnt
          self._order_attempt_size = self.lot_size
        else:
          self._logger.info('Order Failed!!')
      elif self._arb_signal_calculator.can_enter_sell():
        self._logger.info('Arb product[Start Sell]: %s, sell: %s' % (
          self._trade_product.symbol, bidp))
        self._is_last_order_return_type = False
        if self.decide_order_success(book.timestamp):
          self._order_submit_tick = self._tick_cnt
          self._order_attempt_size = -self.lot_size
        else:
          self._logger.info('Order Failed!!')
    if self._korder_tick_delay == 0:
      self.process_order_with_delay(self._tick_cnt, askp, bidp)
    return

class SimpleArbM2_V2SubStrategy(SingleProductSubStrategyArbBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)
    
    self._karb_diff_bar = float(self._config.get('arb_diff_bar', 0.5))

    self._current_quote_value = 0.0
    self._total_profit = 0.0
    self._total_net_profit = 0.0
    self._tx_count = 0

    self._kpos_mismatch_bar = 0.05

    self._karb_diff_bar = 2.0
    self._karb_leave_diff_bar = -0.5
    self._karb_stop_price_diff = 3.0
    self._ktick_ts_diff_allowed = 300000000
    self._last_buy_price = 0.0
    self._last_sell_price = 0.0
    self._arb_signal_calculator = ArbSignalCalculator(queue_size=2, enter_bar=self._karb_diff_bar, leave_bar=self._karb_leave_diff_bar)

    self._korder_cooldown = 5000000000
    self._korder_new_interval = 60000000000
    self._tick_cnt = 0
    self._last_order_ts = 0
    self._is_last_order_success = True
    self._is_last_order_return_type = True
    self._korder_safe_cooldown = 20000000000

    self._korder_tick_delay = 1
    self._order_attempt_size = 0.0
    self._order_submit_tick = 0

  def process_order_with_delay(self, current_tick, ask0, bid0):
    if self._order_submit_tick == 0 or abs(self._order_attempt_size) < 0.0001:
      return
    if current_tick >= self._order_submit_tick + self._korder_tick_delay:
      if self._order_attempt_size > 0.0:
        self._logger.info('Arb product[real price]: buy: %s' % (ask0))
      else:
        self._logger.info('Arb product[real price]: sell: %s' % (bid0))
      self.update_balance_profit(self._order_attempt_size, ask0, bid0)
      if not self._is_last_order_return_type:
        if self._order_attempt_size > 0.0:
          self._last_buy_price = ask0
        else:
          self._last_sell_price = bid0
      else:
        self._last_buy_price = 0.0
        self._last_sell_price = 0.0
      self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
        self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
      self._order_attempt_size = 0.0
      self._order_submit_tick = 0



  def decide_order_success(self, current_ts):
    self._last_order_ts = current_ts
    return True
    #if current_ts - self._last_order_ts > self._korder_new_interval:
    #  self._last_order_ts = current_ts
    #  self._is_last_order_success = False
    #  return False
    
    self._last_order_ts = current_ts
    self._is_last_order_success = not self._is_last_order_success
    return self._is_last_order_success

  def update_balance_profit(self, trade_lot, ask_price, bid_price):
    self._tx_count += 1
    self.current_pos += trade_lot
    if trade_lot > 0:
      self._current_quote_value -= trade_lot * ask_price
    elif trade_lot < 0:
      self._current_quote_value -= trade_lot * bid_price
    base_to_quote = 0.0
    if self.current_pos > self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * bid_price
    elif self.current_pos < self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * ask_price
    self._total_profit = self._current_quote_value + base_to_quote
    self._total_net_profit = self._total_profit - float(self._tx_count) * self._kgas_fee


  def on_book_process(self, product, book):
    if product != self._trade_product:
      return
    
    askp, bidp = book.ask0().price, book.bid0().price
    binance_askp = self._last_binance_ask0
    binance_bidp = self._last_binance_bid0
    self._arb_signal_calculator.insert_record(book.timestamp, askp, bidp, binance_askp, binance_bidp)
    self._tick_cnt += 1

    if self._tick_cnt < 5:
      return
    
    if self._korder_tick_delay > 0:
      self.process_order_with_delay(self._tick_cnt, askp, bidp)

    if self._is_last_order_return_type and book.timestamp - self._last_order_ts < self._korder_safe_cooldown \
      or not self._is_last_order_return_type and book.timestamp - self._last_order_ts < self._korder_cooldown:
      return

    if book.timestamp - self._last_binance_book_ts > self._ktick_ts_diff_allowed:
      return

    if self.current_pos > self.reserve_pos + self._kpos_mismatch_bar:
      if self._last_buy_price - askp > self._karb_stop_price_diff or self._arb_signal_calculator.can_return_buy():
        self._logger.info('Arb product[Return BUY]: %s, sell: %s' % (
          self._trade_product.symbol, bidp))
        self._is_last_order_return_type = True
        if self.decide_order_success(book.timestamp):
          self._order_submit_tick = self._tick_cnt
          self._order_attempt_size = -self.lot_size
        else:
          self._logger.info('Order Failed!!')
    elif self.current_pos < self.reserve_pos - self._kpos_mismatch_bar:
      if bidp - self._last_sell_price > self._karb_stop_price_diff or self._arb_signal_calculator.can_return_sell():
        self._logger.info('Arb product[Return SELL]: %s, buy: %s' % (
          self._trade_product.symbol, askp))
        self._is_last_order_return_type = True
        if self.decide_order_success(book.timestamp):
          self._order_submit_tick = self._tick_cnt
          self._order_attempt_size = self.lot_size
        else:
          self._logger.info('Order Failed!!')
    else:
      if self._arb_signal_calculator.can_enter_buy():
        self._logger.info('Arb product[Start Buy]: %s, buy: %s' % (
          self._trade_product.symbol, askp))
        self._is_last_order_return_type = False
        if self.decide_order_success(book.timestamp):
          self._order_submit_tick = self._tick_cnt
          self._order_attempt_size = self.lot_size
        else:
          self._logger.info('Order Failed!!')
      elif self._arb_signal_calculator.can_enter_sell():
        self._logger.info('Arb product[Start Sell]: %s, sell: %s' % (
          self._trade_product.symbol, bidp))
        self._is_last_order_return_type = False
        if self.decide_order_success(book.timestamp):
          self._order_submit_tick = self._tick_cnt
          self._order_attempt_size = -self.lot_size
        else:
          self._logger.info('Order Failed!!')
    if self._korder_tick_delay == 0:
      self.process_order_with_delay(self._tick_cnt, askp, bidp)
    return

class SimpleArbM2SubStrategy(SingleProductSubStrategyArbBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)
    
    self._karb_diff_bar = float(self._config.get('arb_diff_bar', 0.5))

    self._current_quote_value = 0.0
    self._total_profit = 0.0
    self._total_net_profit = 0.0
    self._tx_count = 0

    self.last_processed_uniswap_ts = 0
    #self._withdraw_bias_calculator = BiasCalculator(max_record_cnt=60, withdraw_signal_bar=1.2 ** 2, provide_signal_bar=0.2 ** 2)
    self._bias_calculator = BiasCalculator(max_record_cnt=240, withdraw_signal_bar=3.0 ** 2, provide_signal_bar=1.5 ** 2)
    #self._small_bias_calculator = BiasCalculator(max_record_cnt=5, withdraw_signal_bar=3.0 ** 2, provide_signal_bar=0.8 ** 2)
    self._kpos_mismatch_bar = 0.05

    self._karb_diff_bar = 2.0
    self._karb_leave_diff_bar = -0.5
    self._karb_stop_price_diff = 3.0
    self._last_buy_price = 0.0
    self._last_sell_price = 0.0

    self._korder_cooldown = 3000000000
    self._korder_new_interval = 60000000000
    self._tick_cnt = 0
    self._last_order_ts = 0
    self._is_last_order_success = True
    self._is_last_order_return_type = True
    self._korder_safe_cooldown = 20000000000

  def decide_order_success(self, current_ts):
    return True
    #if current_ts - self._last_order_ts > self._korder_new_interval:
    #  self._last_order_ts = current_ts
    #  self._is_last_order_success = False
    #  return False
    
    self._last_order_ts = current_ts
    self._is_last_order_success = not self._is_last_order_success
    return self._is_last_order_success

  def update_balance_profit(self, trade_lot, ask_price, bid_price):
    self._tx_count += 1
    self.current_pos += trade_lot
    if trade_lot > 0:
      self._current_quote_value -= trade_lot * ask_price
    elif trade_lot < 0:
      self._current_quote_value -= trade_lot * bid_price
    base_to_quote = 0.0
    if self.current_pos > self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * bid_price
    elif self.current_pos < self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * ask_price
    self._total_profit = self._current_quote_value + base_to_quote
    self._total_net_profit = self._total_profit - float(self._tx_count) * self._kgas_fee

  def process_new_tick_bias(self, uniswap_ts, uniswap_price, binance_price):
    if uniswap_ts == self.last_processed_uniswap_ts:
      return
    self.last_processed_uniswap_ts = uniswap_ts
    self._bias_calculator.insert_record(binance_price, uniswap_price)
    #self._small_bias_calculator.insert_record(binance_price, uniswap_price)
    #self._withdraw_bias_calculator.insert_record(binance_price, uniswap_price)
    self._bias_calculator.print_info(self._logger)
    #self._small_bias_calculator.print_info(self._logger)
    #self._withdraw_bias_calculator.print_info(self._logger)

  def on_book_process(self, product, book):
    if product != self._trade_product:
      askp = self._last_uniswap_ask0
      bidp = self._last_uniswap_bid0
      binance_askp = book.ask0().price
      binance_bidp = book.bid0().price
    else:
      askp, bidp = book.ask0().price, book.bid0().price
      binance_askp = self._last_binance_ask0
      binance_bidp = self._last_binance_bid0
      self._tick_cnt += 1

    if self._tick_cnt < 5:
      return
    
    if self._is_last_order_return_type and book.timestamp - self._last_order_ts < self._korder_safe_cooldown \
      or not self._is_last_order_return_type and book.timestamp - self._last_order_ts < self._korder_cooldown:
      return

    if self.current_pos > self.reserve_pos + self._kpos_mismatch_bar:
      #if self._bias_calculator.should_provide() or self._small_bias_calculator.get_bias_val() < -self._small_bias_calculator._kprovide_signal_bar:
      if self._last_buy_price - askp > self._karb_stop_price_diff or binance_bidp < askp + self._karb_leave_diff_bar:
        self._logger.info('Arb product[Return BUY]: %s, sell: %s' % (
          self._trade_product.symbol, bidp))
        self._is_last_order_return_type = True
        if self.decide_order_success(book.timestamp):
          self.update_balance_profit(-self.lot_size, askp, bidp)
          self._last_buy_price = 0.0
          self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
            self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
        else:
          self._logger.info('Order Failed!!')
    elif self.current_pos < self.reserve_pos - self._kpos_mismatch_bar:
      #if self._bias_calculator.should_provide() or self._small_bias_calculator.get_bias_val() > self._small_bias_calculator._kprovide_signal_bar:
      if bidp - self._last_sell_price > self._karb_stop_price_diff or binance_askp > bidp - self._karb_leave_diff_bar:
        self._logger.info('Arb product[Return SELL]: %s, buy: %s' % (
          self._trade_product.symbol, askp))
        self._is_last_order_return_type = True
        if self.decide_order_success(book.timestamp):
          self.update_balance_profit(self.lot_size, askp, bidp)
          self._last_sell_price = 0.0
          self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
            self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
        else:
          self._logger.info('Order Failed!!')
    else:
      #if self._bias_calculator.should_withdraw() or self._small_bias_calculator.should_withdraw():
      if binance_bidp > askp + self._karb_diff_bar:
        self._logger.info('Arb product[Start Buy]: %s, buy: %s' % (
          self._trade_product.symbol, askp))
        self._is_last_order_return_type = False
        if self.decide_order_success(book.timestamp):
          self.update_balance_profit(self.lot_size, askp, bidp)
          self._last_buy_price = askp
          self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
            self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
        else:
          self._logger.info('Order Failed!!')
      elif binance_askp < bidp - self._karb_diff_bar:
        self._logger.info('Arb product[Start Sell]: %s, sell: %s' % (
          self._trade_product.symbol, bidp))
        self._is_last_order_return_type = False
        if self.decide_order_success(book.timestamp):
          self.update_balance_profit(-self.lot_size, askp, bidp)
          self._last_sell_price = bidp
          self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
            self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
        else:
          self._logger.info('Order Failed!!')
    
    return

class SimpleArbM3SubStrategy(SingleProductSubStrategyArbBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)
    
    self._karb_diff_bar = float(self._config.get('arb_diff_bar', 0.5))

    self._current_quote_value = 0.0
    self._total_profit = 0.0
    self._total_net_profit = 0.0
    self._tx_count = 0

    self.last_processed_uniswap_ts = 0
    #self._withdraw_bias_calculator = BiasCalculator(max_record_cnt=60, withdraw_signal_bar=1.2 ** 2, provide_signal_bar=0.2 ** 2)
    self._bias_calculator = BiasCalculator(max_record_cnt=240, withdraw_signal_bar=3.0 ** 2, provide_signal_bar=1.5 ** 2)
    #self._small_bias_calculator = BiasCalculator(max_record_cnt=5, withdraw_signal_bar=3.0 ** 2, provide_signal_bar=0.8 ** 2)
    self._kpos_mismatch_bar = 0.05
    self._korder_cooldown = 5000000000
    self._korder_new_interval = 60000000000
    self._last_order_ts = 0
    self._is_last_order_success = True

  def decide_order_success(self, current_ts):
    if current_ts - self._last_order_ts > self._korder_new_interval:
      self._last_order_ts = current_ts
      self._is_last_order_success = False
      return False
    
    self._last_order_ts = current_ts
    self._is_last_order_success = not self._is_last_order_success
    return self._is_last_order_success

  def update_balance_profit(self, trade_lot, ask_price, bid_price):
    self._tx_count += 1
    self.current_pos += trade_lot
    if trade_lot > 0:
      self._current_quote_value -= trade_lot * ask_price
    elif trade_lot < 0:
      self._current_quote_value -= trade_lot * bid_price
    base_to_quote = 0.0
    if self.current_pos > self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * bid_price
    elif self.current_pos < self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * ask_price
    self._total_profit = self._current_quote_value + base_to_quote
    self._total_net_profit = self._total_profit - float(self._tx_count) * self._kgas_fee

  def process_new_tick_bias(self, uniswap_ts, uniswap_price, binance_price):
    if uniswap_ts == self.last_processed_uniswap_ts:
      return
    self.last_processed_uniswap_ts = uniswap_ts
    self._bias_calculator.insert_record(binance_price, uniswap_price)
    #self._small_bias_calculator.insert_record(binance_price, uniswap_price)
    #self._withdraw_bias_calculator.insert_record(binance_price, uniswap_price)
    self._bias_calculator.print_info(self._logger)
    #self._small_bias_calculator.print_info(self._logger)
    #self._withdraw_bias_calculator.print_info(self._logger)

  def on_book_process(self, product, book):
    if product.exchange == 'Uniswap':
      if abs(book.timestamp - self._last_binance_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(book.timestamp, (book.ask0().price + book.bid0().price) / 2.0, (self._last_binance_ask0 + self._last_binance_bid0) / 2.0)

    elif product.exchange == 'Binance':
      if abs(book.timestamp - self._last_uniswap_book_ts) <= self._kmax_diff_ms * 10 ** 6:
        self.process_new_tick_bias(self._last_uniswap_book_ts, (self._last_uniswap_ask0 + self._last_uniswap_bid0) / 2.0, (book.ask0().price + book.bid0().price) / 2.0)

    if product != self._trade_product:
      return
    
    askp, bidp = book.ask0().price, book.bid0().price
    midp = (float)(askp + bidp) / 2.0
    self._logger.info('mid price:%f' % (midp))

    #if not self._bias_calculator.has_enough_record() or not self._small_bias_calculator.has_enough_record():
    #  return
    if not self._bias_calculator.has_enough_record():
      return
    
    if book.timestamp - self._last_order_ts < self._korder_cooldown:
      return

    if self.current_pos > self.reserve_pos + self._kpos_mismatch_bar:
      #if self._bias_calculator.should_provide() or self._small_bias_calculator.get_bias_val() < -self._small_bias_calculator._kprovide_signal_bar:
      if self._bias_calculator.should_provide():
        self._logger.info('Arb product[Return BUY]: %s, sell: %s' % (
          self._trade_product.symbol, bidp))
        if self.decide_order_success(book.timestamp):
          self.update_balance_profit(-self.lot_size, askp, bidp)
          self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
            self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
        else:
          self._logger.info('Order Failed!!')
    elif self.current_pos < self.reserve_pos - self._kpos_mismatch_bar:
      #if self._bias_calculator.should_provide() or self._small_bias_calculator.get_bias_val() > self._small_bias_calculator._kprovide_signal_bar:
      if self._bias_calculator.should_provide():
        self._logger.info('Arb product[Return SELL]: %s, buy: %s' % (
          self._trade_product.symbol, askp))
        if self.decide_order_success(book.timestamp):
          self.update_balance_profit(self.lot_size, askp, bidp)
          self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
            self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
        else:
          self._logger.info('Order Failed!!')
    else:
      #if self._bias_calculator.should_withdraw() or self._small_bias_calculator.should_withdraw():
      if self._bias_calculator.should_withdraw():
        if self._bias_calculator.get_bias_val() > 0.0:
          self._logger.info('Arb product[Start Buy]: %s, buy: %s' % (
            self._trade_product.symbol, askp))
          if self.decide_order_success(book.timestamp):
            self.update_balance_profit(self.lot_size, askp, bidp)
            self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
              self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
          else:
            self._logger.info('Order Failed!!')
        else:
          self._logger.info('Arb product[Start Sell]: %s, sell: %s' % (
            self._trade_product.symbol, bidp))
          if self.decide_order_success(book.timestamp):
            self.update_balance_profit(-self.lot_size, askp, bidp)
            self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
              self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
          else:
            self._logger.info('Order Failed!!')
    
    return

class SimpleArbM1SubStrategy(SingleProductSubStrategyArbBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)
    
    self._karb_diff_bar = float(self._config.get('arb_diff_bar', 0.5))

    self._current_quote_value = 0.0
    self._total_profit = 0.0
    self._total_net_profit = 0.0
    self._tx_count = 0

    self._korder_cooldown = 4000000000
    self._last_order_ts = 0
    self._is_last_order_success = True

  def decide_order_success(self, current_ts):
    return True
    self._last_order_ts = current_ts
    self._is_last_order_success = not self._is_last_order_success
    return self._is_last_order_success

  def update_balance_profit(self, trade_lot, ask_price, bid_price):
    self._tx_count += 1
    self.current_pos += trade_lot
    if trade_lot > 0:
      self._current_quote_value -= trade_lot * ask_price
    elif trade_lot < 0:
      self._current_quote_value -= trade_lot * bid_price
    base_to_quote = 0.0
    if self.current_pos > self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * bid_price
    elif self.current_pos < self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * ask_price
    self._total_profit = self._current_quote_value + base_to_quote
    self._total_net_profit = self._total_profit - float(self._tx_count) * self._kgas_fee

  def on_book_process(self, product, book):
    if product != self._trade_product:
      return
    askp, bidp = book.ask0().price, book.bid0().price
    #pos = self._executor.query_unbiased_position()
    
    # update ask/bid price based on current position
    # e.g ETH is lower the the reserved amount, then ask/bid both rise.
    # because it will result in eaiser BUY(buy ETH)
    new_askp, new_bidp = self._pos_lean_pricer.update(pos=self.current_pos, buy_price=bidp, sell_price=askp)
    askp = askp - (new_askp - askp)
    bidp = bidp - (new_bidp - bidp)

    self._logger.info('Calculated product: %s, sell: %s, buy: %s, pos:%s' % (
        self._trade_product.symbol, askp, bidp, self.current_pos))
    
    #if book.timestamp - self._last_order_ts < self._korder_cooldown:
    #  return

    # t_0 is the price for ref product(maybe some kind of avg price??), t_1 is the price for trade product
    if askp <= self._pricer.last_value.bidt_0 - self._karb_diff_bar:
      if self.current_pos >= self.max_pos - 1 / 10 ** 2:
        return
      if self.decide_order_success(book.timestamp):
        self._logger.info('Arb product: %s, buy: %s, pretend sell: %s' % (
            self._trade_product.symbol, self._pricer.last_value.askt_1, self._pricer.last_value.bidt_0))
        self.update_balance_profit(self.lot_size, self._pricer.last_value.askt_1, self._pricer.last_value.bidt_1)
        self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
          self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
        return
    elif bidp >= self._pricer.last_value.askt_0 + self._karb_diff_bar:
      if self.current_pos <= self.min_pos + 1 / 10 ** 2:
        return
      if self.decide_order_success(book.timestamp):
        self._logger.info('Arb product: %s, sell: %s, pretend buy: %s' % (
            self._trade_product.symbol, self._pricer.last_value.bidt_1, self._pricer.last_value.askt_0))
        self.update_balance_profit(-self.lot_size, self._pricer.last_value.askt_1, self._pricer.last_value.bidt_1)
        self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
          self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
        return
    
    return

class SimpleArbM1_2SubStrategy(SingleProductSubStrategyArbBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)

    self._kmax_diff_ms = int(self._config.get('max_diff_ms', 500))

    self._kgas_fee = float(self._config.get('gas_fee', 0.5))
    self._karb_enter_bar = float(self._config.get('arb_enter_bar', 0.5))
    self._kamount_per_lot = int(self._config.get('amount_per_lot', 10))
    self._kprofit_target_per_tx = float(self._config.get('profit_target_per_tx', 4.0))

    self._kprofit_delta = (2 * self._kgas_fee + self._kprofit_target_per_tx) / float(self._kamount_per_lot)

    self._last_binance_book_ts = 0
    self._last_uniswap_book_ts = 0
    self._last_binance_ask0 = 0.0
    self._last_binance_bid0 = 0.0
    self._last_uniswap_ask0 = 0.0
    self._last_uniswap_bid0 = 0.0
    self._pos_lot_cnt = 0
    self._uniswap_enter_price = 0.0
    self._uniswap_enter_opp_price = 0.0
    self._binance_target_price = 0.0
    self._profit_target_price = 0.0
    self._total_profit = 0.0
    
    self._karb_diff_bar = float(self._config.get('arb_diff_bar', 0.5))
    self._karb_lower_diff_bar = float(self._config.get('arb_lower_diff_bar', 0.0))
    self.reserve_pos = float(self._config.get('reserve', 0.5))
    self.max_pos = float(self._config.get('max_pos', 0.9))
    self.min_pos = float(self._config.get('min_pos', 0.1))
    self.current_pos = self.reserve_pos
    self.lot_size = float(self._config.get('lot_size', 0.1))
    self._current_quote_value = 0.0
    self._total_net_profit = 0.0
    self._tx_count = 0

  def update_balance_profit(self, trade_lot, ask_price, bid_price):
    self._tx_count += 1
    self.current_pos += trade_lot
    if trade_lot > 0:
      self._current_quote_value -= trade_lot * ask_price
    elif trade_lot < 0:
      self._current_quote_value -= trade_lot * bid_price
    base_to_quote = 0.0
    if self.current_pos > self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * bid_price
    elif self.current_pos < self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * ask_price
    self._total_profit = self._current_quote_value + base_to_quote
    self._total_net_profit = self._total_profit - float(self._tx_count) * self._kgas_fee

  def on_book_process(self, product, book):
    if product != self._trade_product:
      return
    askp, bidp = book.ask0().price, book.bid0().price
    #pos = self._executor.query_unbiased_position()
    
    # update ask/bid price based on current position
    # e.g ETH is lower the the reserved amount, then ask/bid both rise.
    # because it will result in eaiser BUY(buy ETH)
    new_askp, new_bidp = self._pos_lean_pricer.update(pos=self.current_pos, buy_price=bidp, sell_price=askp)
    askp = askp - (new_askp - askp)
    bidp = bidp - (new_bidp - bidp)

    self._logger.info('Calculated product: %s, sell: %s, buy: %s, pos:%s' % (
        self._trade_product.symbol, askp, bidp, self.current_pos))
    
    # t_0 is the price for ref product(maybe some kind of avg price??), t_1 is the price for trade product
    if askp <= self._pricer.last_value.bidt_0 - self._karb_diff_bar or self.current_pos < self.reserve_pos - 1 / 10 ** 2 and askp <= self._pricer.last_value.bidt_0 - self._karb_lower_diff_bar:
      if self.current_pos >= self.max_pos - 1 / 10 ** 2:
        return
      self._logger.info('Arb product: %s, buy: %s, pretend sell: %s' % (
          self._trade_product.symbol, self._pricer.last_value.askt_1, self._pricer.last_value.bidt_0))
      self.update_balance_profit(self.lot_size, self._pricer.last_value.askt_1, self._pricer.last_value.bidt_1)
      self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
        self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
      return
    elif bidp >= self._pricer.last_value.askt_0 + self._karb_diff_bar or self.current_pos > self.reserve_pos + 1 / 10 ** 2 and bidp >= self._pricer.last_value.askt_0 + self._karb_lower_diff_bar:
      if self.current_pos <= self.min_pos + 1 / 10 ** 2:
        return
      self._logger.info('Arb product: %s, sell: %s, pretend buy: %s' % (
          self._trade_product.symbol, self._pricer.last_value.bidt_1, self._pricer.last_value.askt_0))
      self.update_balance_profit(-self.lot_size, self._pricer.last_value.askt_1, self._pricer.last_value.bidt_1)
      self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
        self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
      return
    
    return

class SimpleArbM1_3SubStrategy(SingleProductSubStrategyArbBase):
  def __init__(self, model_profile, logger, parent):
    super().__init__(model_profile, logger, parent)

    self._kmax_diff_ms = int(self._config.get('max_diff_ms', 500))

    self._kgas_fee = float(self._config.get('gas_fee', 0.5))
    self._karb_enter_bar = float(self._config.get('arb_enter_bar', 0.5))
    self._kamount_per_lot = int(self._config.get('amount_per_lot', 10))
    self._kprofit_target_per_tx = float(self._config.get('profit_target_per_tx', 4.0))

    self._kprofit_delta = (2 * self._kgas_fee + self._kprofit_target_per_tx) / float(self._kamount_per_lot)

    self._last_binance_book_ts = 0
    self._last_uniswap_book_ts = 0
    self._last_binance_ask0 = 0.0
    self._last_binance_bid0 = 0.0
    self._last_uniswap_ask0 = 0.0
    self._last_uniswap_bid0 = 0.0
    self._pos_lot_cnt = 0
    self._uniswap_enter_price = 0.0
    self._uniswap_enter_opp_price = 0.0
    self._binance_target_price = 0.0
    self._profit_target_price = 0.0
    self._total_profit = 0.0
    
    self._karb_diff_bar = float(self._config.get('arb_diff_bar', 0.5))
    self._karb_lower_diff_bar = float(self._config.get('arb_lower_diff_bar', 0.0))
    self.reserve_pos = float(self._config.get('reserve', 0.5))
    self.max_pos = float(self._config.get('max_pos', 0.9))
    self.min_pos = float(self._config.get('min_pos', 0.1))
    self.current_pos = self.reserve_pos
    self.lot_size = float(self._config.get('lot_size', 0.1))
    self._current_quote_value = 0.0
    self._total_net_profit = 0.0
    self._tx_count = 0
    self._signal_cal = SignalCalculator()

  def update_balance_profit(self, trade_lot, ask_price, bid_price):
    self._tx_count += 1
    self.current_pos += trade_lot
    if trade_lot > 0:
      self._current_quote_value -= trade_lot * ask_price
    elif trade_lot < 0:
      self._current_quote_value -= trade_lot * bid_price
    base_to_quote = 0.0
    if self.current_pos > self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * bid_price
    elif self.current_pos < self.reserve_pos:
      base_to_quote = (self.current_pos - self.reserve_pos) * ask_price
    self._total_profit = self._current_quote_value + base_to_quote
    self._total_net_profit = self._total_profit - float(self._tx_count) * self._kgas_fee

  def on_book_process(self, product, book):
    if product != self._trade_product:
      return
    askp, bidp = book.ask0().price, book.bid0().price
    #pos = self._executor.query_unbiased_position()
    
    # update ask/bid price based on current position
    # e.g ETH is lower the the reserved amount, then ask/bid both rise.
    # because it will result in eaiser BUY(buy ETH)
    new_askp, new_bidp = self._pos_lean_pricer.update(pos=self.current_pos, buy_price=bidp, sell_price=askp)
    askp = askp - (new_askp - askp)
    bidp = bidp - (new_bidp - bidp)

    self._logger.info('Calculated product: %s, sell: %s, buy: %s, pos:%s' % (
        self._trade_product.symbol, askp, bidp, self.current_pos))
    
    # t_0 is the price for ref product(maybe some kind of avg price??), t_1 is the price for trade product
    if askp <= self._pricer.last_value.bidt_0 - self._karb_diff_bar:
      self._signal_cal.add_signal(1)
      self._logger.info('plus percentage: %s' % (self._signal_cal.get_plus_percentage()))
      if self.current_pos >= self.max_pos - 1 / 10 ** 2:
        return
      if self._signal_cal.has_plus_signal(self.current_pos < self.reserve_pos - 1 / 10 ** 2):
        self._logger.info('Arb product: %s, buy: %s, pretend sell: %s' % (
            self._trade_product.symbol, self._pricer.last_value.askt_1, self._pricer.last_value.bidt_0))
        self._signal_cal.clear()
        #if self.current_pos >= self.reserve_pos - 1 / 10 ** 2:
          #self._signal_cal.clear()
          #self._cur_target_price = self._pricer.last_value.askt_1 + self._ktarget_profit
        self.update_balance_profit(self.lot_size, self._pricer.last_value.askt_1, self._pricer.last_value.bidt_1)
        self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
          self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
        return
    elif bidp >= self._pricer.last_value.askt_0 + self._karb_diff_bar:
      self._signal_cal.add_signal(-1)
      self._logger.info('minus percentage: %s' % (self._signal_cal.get_minus_percentage()))
      if self.current_pos <= self.min_pos + 1 / 10 ** 2:
        return
      if self._signal_cal.has_minus_signal(self.current_pos > self.reserve_pos + 1 / 10 ** 2):
        self._logger.info('Arb product: %s, sell: %s, pretend buy: %s' % (
            self._trade_product.symbol, self._pricer.last_value.bidt_1, self._pricer.last_value.askt_0))
        self._signal_cal.clear()
        #if self.current_pos <= self.reserve_pos + 1 / 10 ** 2:
          #self._signal_cal.clear()
          #self._cur_target_price = self._pricer.last_value.bidt_1 - self._ktarget_profit
        self.update_balance_profit(-self.lot_size, self._pricer.last_value.askt_1, self._pricer.last_value.bidt_1)
        self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
          self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
      return
    else:
      self._signal_cal.add_signal(0)
      '''
      if self.current_pos > self.reserve_pos + 1 / 10 ** 2 and self._pricer.last_value.bidt_1 > self._cur_target_price:
        self._logger.info('restore product: %s, sell: %s' % (
              self._trade_product.symbol, self._pricer.last_value.bidt_1))
        self.update_balance_profit(-self.lot_size, self._pricer.last_value.askt_1, self._pricer.last_value.bidt_1)
        self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
            self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
      elif self.current_pos < self.reserve_pos - 1 / 10 ** 2 and self._pricer.last_value.askt_1 < self._cur_target_price:
        self._logger.info('restore product: %s, buy: %s' % (
              self._trade_product.symbol, self._pricer.last_value.askt_1))
        self.update_balance_profit(self.lot_size, self._pricer.last_value.askt_1, self._pricer.last_value.bidt_1)
        self._logger.info('current ETH:%s, current USDC:%s, current profit:%s, current net profit:%s' % (
            self.current_pos, self._current_quote_value, self._total_profit, self._total_net_profit))
      '''
    
    return

class SimpleArbStrategy(UnaryMultifeedStrategyBase):
  def __init__(self, strat_config, logger=None):
    self._logger = logger or logging.getLogger('SimpleArbStrategy')
    assert isinstance(strat_config, PassUnhedgeStrategyConfig)
    self._config = strat_config
    self._set_traits(self._config.trade_subreq)
    super().__init__(products=self._config.get_products(),
                     currencies=self._config.get_currencies(),
                     logger=self._logger)

    self._book = BookMap(self._products, logger=self._logger)

    self._reserve_map = self._gen_reserve_map()
    printer_cls = get_pnl_balance_printer_cls(self._config.trade_subreq)
    self._printer = printer_cls(self._products, self._book, self._reserve_map, logger=self._logger)

    self._fill_dumper = FillLogDumper(self._config.trade_subreq)

    self._order_canceled_after_og_ready = True
    self._exit_attempt = 0
    self._exit_executor = None

    self._single_product_sub_strategies = []
    for profile in self._config.model_profile.values():
      self._single_product_sub_strategies.append(
          SimpleArbGeneralM2_V2SubStrategy(model_profile=profile,
                                   logger=self._logger,
                                   parent=self))

  @property
  def config(self):
    return self._config

  def _gen_reserve_map(self):
    reserve_map = {}
    if self._config.trade_subreq.market_type == 'Spot':
      for profile in self._config.model_profile.values():
        currency = profile.trade_product.base.currency
        min_pos = profile.config_dict['min_pos']
        max_pos = profile.config_dict['max_pos']
        reserve = 0.5 * (min_pos + max_pos)
        reserve_map[currency] = reserve
    return reserve_map

  def prepare(self):
    product_list = []
    for profile in self.config.model_profile.values():
      product = profile.trade_product
      product_list.append(product)
      product = profile.ref_product
      product_list.extend(product)
    define_fastfeed_flags()
    trading_date_start = self._config.config['sim']["trading_date_start"]
    trading_date_end = self._config.config['sim']["trading_date_end"]
    start_date = datetime.strptime(trading_date_start, "%Y%m%d")
    end_date = datetime.strptime(trading_date_end, "%Y%m%d")
    delta = timedelta(days=1)
    while start_date <= end_date:
        run_from_archive_or_cache(
          start_date.strftime("%Y%m%d"), FLAGS.feed_machine, product_list, self, True)
        start_date += delta
    

  def on_book_reset(self, products, book_builder_name, book_builder, feed_cache_key=None):
    for product in products:
        book_builder.subscribe_book(product, functools.partial(self.on_book, product))
        book_builder.subscribe_trade(product, functools.partial(self.on_trade, product))

  def on_book(self, product, book):
    for strat in self._single_product_sub_strategies:
      strat.on_book(product, book)

  def on_trade(self, product, trade):
    for strat in self._single_product_sub_strategies:
      strat.on_trade(product, trade)

  def signal_handler(self, sig, frame):
    self._logger.warning('Caught signal: %s', sig)
    self._ioloop.add_callback_from_signal(self.on_exit)

  def register_exit(self, timedelta=None):
    if timedelta is None:
      self._ioloop.add_callback(self.on_exit)
    else:
      self._ioloop.add_timeout(timedelta, self.on_exit)

  def on_exit(self):
    for strat in self._single_product_sub_strategies:
      strat.set_exit()

    self._exit_attempt += 1
    if self._exit_attempt > 5:
      self._ioloop.add_callback(self._ioloop.stop)

    finished = True
    if self._exit_executor is not None:
      ogs = to_list(self.order_gateway)
      self._logger.info('Exit callback executed.')
      finished = self._exit_executor.cancel_all_on_exit(ogs)

    if finished:
      self._ioloop.add_callback(self._ioloop.stop)
    else:
      timeout = datetime.timedelta(seconds=2)
      self._ioloop.add_timeout(timeout, self.on_exit)

  def run(self):
    self.prepare()


def main(argv):
  strat_config = PassUnhedgeStrategyConfig(load_clean_json(FLAGS.config_filename))
  strategy = SimpleArbStrategy(strat_config)
  signal.signal(signal.SIGTERM, strategy.signal_handler)
  signal.signal(signal.SIGINT, strategy.signal_handler)

  return strategy.run()


if __name__ == '__main__':
  flags.DEFINE_string('og_config_name', None, 'og_config_name')
  flags.DEFINE_string('config_filename', '', 'config_filename')
  flags.DEFINE_integer('exit_after_min', 1440, 'exit_after_min')
  flags.DEFINE_boolean('dry_run', False, 'dry_run')

  flags.DEFINE_float('arb_diff_bar', 0.001, 'arb_diff_bar')
  flags.DEFINE_float('arb_leave_bar', 0.00025, 'arb_leave_bar')
  flags.DEFINE_integer('queue_size', 1, 'queue_size')

  logging.basicConfig(level='INFO', format='%(levelname)s %(asctime)s %(name)s] %(message)s')
  app.run(main)
